% class ETData: storage for current eddy sensor data and temperature
classdef  ETData < handle
    properties (SetAccess = private)
        name    % name of data folder
        Time    % time data(vector)
        Eddy    % eddy data(matrix)
        Temp    % temperature data(matrix)
        Elabel  % legends of eddy(cell array of string)
        Tlabel  % legends of time(cell array of string)
        nEddy   % number of eddy channels(double)
        nTemp   % number of temperature channels(double)
        m       % number of samples(double)
    end

    methods
        % Constructor
        % Argument:
        %   folder: data folder(char vector)
        %   level : priority level for reading data files(numeric)
        %     1: start from MAT file
        %     2: start from TXT file
        % Return:
        %   obj: object handle
        function obj = ETData(folder, level)
            % check parameters
            if nargin == 0
                return;
            elseif nargin == 1
                level = 1;
            elseif nargin > 2
                error('too many arguments');
            end

            if ~exist(folder, 'dir')
                error(['folder: ''', folder, ''' not exist']);
            end

            mpath = fullfile(folder, [folder, '.mat']);

            % 1st priority: load from MAT file
            if level < 2
                if exist(mpath, 'file')
                    %% message
                    disp('construct object from MAT file');

                    load(mpath, 'obj');

                    if isvalid(obj)
                        return; % load obj success
                    else
                        error('no var ''obj'' in MAT file');
                    end
                end
            end

            % 2nd priority: load from TXT file
            if level < 3
                %% message
                disp('constrcut object from TXT file');

                % assign folder to name
                obj.name = folder;

                % initialize file path
                fpath    = cell(1, 4);
                fname    = cell(1, 4);
                fpath{1} = fullfile(folder, 'ET*.json');
                fpath{2} = fullfile(folder, 'ET*_Time.txt');
                fpath{3} = fullfile(folder, 'ET*_Eddy.txt');
                fpath{4} = fullfile(folder, 'ET*_Temp.txt');

                for i = 1 : 4
                    fdir = dir(fpath{i});

                    n = numel(fdir);
                    if n == 0
                        error([fpath{i}, ' do not exist']);
                    elseif n > 1
                        error([fpath{i}, ' matches more than one']);
                    end

                    fname{i} = fullfile(folder, fdir(1).name);
                end

                %% read json file
                [obj.Elabel, obj.Tlabel] = getLegend(fname{1});

                nElabel = numel(obj.Elabel);
                nTlabel = numel(obj.Tlabel);

                %% read data files
                obj.Time = load(fname{2});
                obj.Eddy = load(fname{3});
                obj.Temp = load(fname{4});

                [m1, ~        ] = size(obj.Time);
                [m2, obj.nEddy] = size(obj.Eddy);
                [m3, obj.nTemp] = size(obj.Temp);

                %% determine if length of samples is consistent
                if m1 ~= m2 || m1 ~= m3
                    error('number of samples is not consistent');
                end

                obj.m = m1; % number of samples

                %% determine if number of labels match that of data
                if nElabel ~= obj.nEddy
                    warning('number of Eddy channels do not match that of labels');
                    if nElabel < obj.nEddy
                        % add label 'unknown'
                        obj.Elabel = [obj.Elabel, repmat({'unknown'}, 1, obj.nEddy - nElabel)];
                    else
                        % remove redundant labels
                        obj.Elabel((1:nElabel) > obj.nEddy) = [];
                    end
                end
                if nTlabel ~= obj.nTemp
                    warning('number of Temp channels do not match that of labels');
                    if nTlabel < obj.nTemp
                        % add label 'unknown'
                        obj.Tlabel = [obj.Tlabel, repmat({'unknown'}, 1, obj.nTemp - nTlabel)];
                    else
                        % remove redundant labels
                        obj.Tlabel((1:nTlabel) > obj.nTemp) = [];
                    end
                end

                %% save data as MAT file
                save(mpath, 'obj');

                return;
            end

        end

        % Function:
        %   get eddy by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        % Return:
        %   r: i'th column of Eddy data
        function r = eget(obj, i)
            r = obj.Eddy(:, i);
        end

        % Function:
        %   set eddy by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        %   E  : eddy data(each column is one channel)
        % Return:
        %   no
        function eset(obj, i, E, label)
            % check nargin
            if nargin < 3
                error('Usage: eset(i, E) or eset(i, E, label)');
            elseif nargin < 4
                label = [];
            end

            if isscalar(i)
                lgc = ((1:obj.nEddy) == i);
            else
                if isrow(i)
                    i = i';
                end
                lgc = logical(sum((1:obj.nEddy) == i));
            end

            if isempty(E)
                % deleting operation
                % message
                for i = 1 : numel(lgc)
                    if lgc(i)
                        disp(['delete channel: ', obj.Elabel{i}]);
                    end
                end

                obj.Eddy(:, lgc) = [];
                obj.Elabel(lgc)  = [];
                obj.nEddy        = obj.nEddy - sum(lgc);
            else
                obj.Eddy(:, lgc) = E;
                if ~isempty(label)
                    obj.Elabel(lgc)  = label;
                end
            end

        end

        % Function:
        %   delete eddy by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        % Return:
        %   no
        function edel(obj, i)
            obj.eset(i, []);
        end

        % Function:
        %   add eddy data to object
        % Argument:
        %   obj  : object handle
        %   E    : eddy data to be added
        %   label: label corresponding to E
        % Return:
        %   no
        function eadd(obj, E, label)
            if ~iscell(label)
                label = {label};
            end

            % check input arguments
            [M, n] = size(E);
            nlabel = numel(label);

            if M ~= obj.m
                error(['number of samples must be ', num2str(obj.m)]);
            end
            if n ~= nlabel
                error('number of labels is not equals to that of data');
            end

            obj.Eddy   = [obj.Eddy, E];
            obj.Elabel = [obj.Elabel, label];
            obj.nEddy  = obj.nEddy + n;
        end

        % Function:
        %   get temp by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        % Return:
        %   r: i'th column of Temp data
        function r = tget(obj, i)
            r = obj.Temp(:, i);
        end

        % Function:
        %   set temp by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        %   T  : temp data(each column is one channel)
        % Return:
        %   no
        function tset(obj, i, T, label)
            % check nargin
            if nargin < 3
                error('Usage: tset(i, T) or tset(i, T, label)');
            elseif nargin < 4
                label = [];
            end

            if isscalar(i)
                lgc = ((1:obj.nTemp) == i);
            else
                if isrow(i)
                    i = i';
                end
                lgc = logical(sum((1:obj.nTemp) == i));
            end

            if isempty(T)
                % message
                for i = 1 : numel(lgc)
                    if lgc(i)
                        disp(['delete channel: ', obj.Tlabel{i}]);
                    end
                end

                obj.Temp(:, lgc) = [];
                obj.Tlabel(lgc)  = [];
                obj.nTemp        = obj.nTemp - sum(lgc);
            else
                obj.Temp(:, lgc) = T;
                if ~isempty(label)
                    obj.Tlabel(lgc) = label;
                end
            end
        end

        % Function:
        %   delete temp by column(channel)
        % Argument:
        %   obj: object handle
        %   i  : index of column
        % Return:
        %   no
        function tdel(obj, i)
            obj.tset(i, []);
        end

        % Function:
        %   add temp data to object
        % Argument:
        %   obj  : object handle
        %   T    : temp data to be added
        %   label: label corresponding to T
        % Return:
        %   no
        function tadd(obj, T, label)
            if ~iscell(label)
                label = {label};
            end

            % check input arguments
            [M, n] = size(T);
            nlabel = numel(label);

            if M ~= obj.m
                error(['number of samples must be ', num2str(obj.m)]);
            end
            if n ~= nlabel
                error('number of labels is not equals to that of data');
            end

            obj.Temp   = [obj.Temp, T];
            obj.Tlabel = [obj.Tlabel, label];
            obj.nTemp  = obj.nTemp + n;
        end

        function comp(obj, i)
            fdir = dir(fullfile(obj.name, 'ET*_Comp.txt'));

            if numel(fdir) ~= 1
                error('no unique file: ET*_Comp.txt');
            end

            Comp = load(fullfile(obj.name, fdir(1).name));

            if isempty(Comp)
                error('empty Comp file');
            else
                % check size of Comp
                [M, n] = size(Comp);

                if M ~= obj.m
                    error('number of samples is not compatible');
                end
                if n ~= 1 && n ~= numel(i)
                    error('number of Comp channels error');
                end

                obj.Eddy(:, i) = obj.Eddy(:, i) - 2 * Comp;
                disp('plus size with Comp value!');
            end
        end

        % Function:
        %   show detail information of this ETData
        % Argument:
        %   obj: object handle
        % Return:
        %   no
        function detail(obj)
            fprintf(1, '>> %s <<\n', obj.name);
            % print information of eddy data
            fprintf(1, 'Eddy(%d chs):\n', obj.nEddy);
            for i = 1 : obj.nEddy
                fprintf(1, '\t%2d: %s\n', i, obj.Elabel{i});
            end
            % print information of temperature data
            fprintf(1, 'Temp(%d chs):\n', obj.nTemp);
            for i = 1 : obj.nTemp
                fprintf(1, '\t%2d: %s\n', i, obj.Tlabel{i});
            end
            % print number of samples
            fprintf(1, 'nSamples: %d\n', obj.m);
            % print sampling time
            t = [obj.Time(1), obj.Time(end)];
            d = fix(t / 1440);
            t = rem(t, 1440);
            h = fix(t / 60);
            M = rem(t, 60);
            fprintf(1, 'Time:\n\t');
            fprintf(1, 'from %dd+%02d:%02d to %dd+%02d:%02d\n',...
                    d(1), h(1), M(1), d(2), h(2), M(2));
        end

        % Function:
        %   save ETData as MAT file
        % Argument:
        %   obj   : this object handle
        %   folder: destination folder(char vector)
        % Return:
        %   no
        function save(obj, folder)
            % check argument
            if nargin == 1
                folder = obj.name;
            end

            if ~exist(folder, 'dir')
                error(['folder: ''', folder, ''' not exist']);
            end
            mpath = fullfile(folder, [obj.name, '.mat']);

            % save data as MAT file
            save(mpath, 'obj');
        end

        % Function:
        %   section analysis on i'th current eddy channels
        % Argument:
        %   obj: object handle
        %   i  : i'th current eddy channels
        %   d  : resolution of eddy value
        % Return:
        %   s: Time value for each endpoint
        function s = esect(obj, i, d)
            if nargin == 2
                d = 0.5;
            end

            s = obj.Time(Section(obj.Eddy(:, i), d));
        end

        % Function:
        %   section analysis on i'th temperature channels
        % Argument:
        %   obj: object handle
        %   i  : i'th temperature channels
        %   d  : resolution of temperature
        % Return:
        %   s: Time value for each endpoint
        function s = tsect(obj, i, d)
            if nargin == 2
                d = 0.2;
            end

            s = obj.Time(Section(obj.Temp(:, i), d));
        end

        % Function:
        %   concatenate two ETData objects and return a new object
        % Argument:
        %   o1: one ETData object
        %   o2: another ETData object
        % Return:
        %   r: new ETData object who contains data from o1 and o2
        r = cate(o1, o2) % function signature

        % Function:
        %   cut data from st to ed
        % Argument:
        %   obj: object handle
        %   st : start of time
        %   ed : end of time
        % Return:
        %   no
        cut(obj, st, ed) % function signature

        % Function:
        %   plot eddy-time and temperature-time graph
        % Argument:
        %   obj     : this object handle
        %   varargin:
        %     {1}: selecting vector of eddy channels
        %     {2}: selecting vector of temp channels
        % Return:
        %   f: current graph handle
        f = plot(obj, varargin) % function signature
    end

    methods (Access = private)
        % Destructor
        % Argument:
        %   obj: object handle
        function delete(obj)
            % message
            if ~isempty(obj.name)
                disp(['destroy ', obj.name]);
            end
        end
    end

    methods (Static)
        % Function:
        %   int time to formated time
        % Argument:
        %   t: int time
        % Return:
        %   f: formated time
        f = tfmt(t) % function signatures
    end
end
